feat(approvals): typed output pickers, quick-path guard, expression completion (framework#3447, #2829) - #2831
Merged
Conversation
…ompletion (framework#3447, #2829) Typed decision_output_defs render record pickers (user people-picker / department/position/team lookups; multiple → id array). Quick decision paths (inline a/r, hover, mobile, bulk) are guarded off requests that declare decision outputs — only the drawer dialog collects them; buttons disable with an explanation. The expression approver input gains a three-group scope picker (current/trigger/vars) with inline root validation; nodeOutputRefs models approval outputs (decision + declared keys) so the previous stage's outputs are pickable, and vars.previous is always listed so legitimate vars.* references never flag as unknown. Browser-verified end to end (picker multi-select → co-sign slate). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This was referenced Jul 29, 2026
hotlong
pushed a commit
that referenced
this pull request
Jul 30, 2026
…#2961) * fix(approvals): decision outputs reach both decision surfaces (#2955) An approval node's `decisionOutputs` (framework#3447 P2) never made it to the approver on either Console surface, though the server has shipped the typed declaration on the request row all along. The Approval Center DID synthesize a typed picker per declared output (#2831), but spelled the picker target `referenceTo`. Every collected param passes through `resolveActionParams()` first, and its inline branch rebuilds the param from a fixed key list, reading the target from `reference` — so the target was dropped and `paramToField()` degraded the targetless picker to a plain text input labelled "<label> 的记录 ID". `user` outputs were unaffected (that widget needs no target); `department`/`position`/`team` were all broken. The record header collected nothing at all: its Approve/Reject shipped their inputs under `collectParams`, a key nothing reads (ActionRunner collects from `actionParams`). No dialog had opened there since ADR-0019 — the comment was silently dropped on every record-page decision, and a node declaring `decisionOutputs` resumed the flow with `vars.<node>.<key>` missing, so the next node's `expression` approver faulted or fell through to `onEmptyApprovers` with no hint to anyone. The widget mapping now lives in `utils/decisionOutputParams` so the two surfaces cannot drift again; the header collects through `actionParams`, folds `outputs.<key>` into the decide body, and its comment box is a real textarea (the resolver drops `multiline`). Tests pin the round trip through param resolution — the stage that actually broke — not just the emitted shape. Verified end to end against the app-showcase runtime: an approval node declaring a `position`-typed multi-select renders a sys_position picker in both the Approval Center drawer and the record header, and deciding from the record page carries the outputs through to `vars.*` — the co-sign node's `expression` approver resolved to the user picked in the dialog. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(approvals): block a decision on a required output (#2955) The platform half of this issue gives `decisionOutputs` a `required` flag: the server rejects an approve that carries no value for one, before any write, so the run can never resume past the node with the key a downstream `expression` approver reads still missing. The console mirrors it where the approver actually is. A required output is marked required on the param it synthesizes, so the dialog flags the empty field and refuses Confirm instead of sending a decision the server will 400. APPROVE only, matching the server: a reject leaves down the reject edge where nothing reads the outputs, so the reject dialog offers the same fields without blocking on them — the two dialogs now differ in exactly that flag. Both decision surfaces get it from the one shared helper, and a backend that predates the field requires nothing, which is the behaviour of the previous commit unchanged. Verified against the showcase runtime (its dynamic-approval flow declares the flag): the record header's Approve refuses with "Next Reviewers 为必填项" and sends no request, its Reject offers the field unmarked, the Approval Center drawer agrees on both, and a filled approve routes the co-sign node to the picked user. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second follow-up wave for framework#3447 (pairs with framework PR objectstack-ai/objectstack#3554). Closes #2829.
1. Typed decision-output pickers.
decision_output_defs({ key, label?, type, multiple? }, surfaced by fw#3554) renders record pickers in the decide dialog:user→ the sys_user people picker,department/position/team→ the matching system-object lookup;multiplecollects an id array. Bare keys keep the free-text input; older backends (key list only) keep working.2. Quick-path guard (#2829). Inline
a/r, hover buttons, mobile card buttons and bulk apply no longer decide a request whose node declares decision outputs — only the drawer dialog collects those fields. Buttons disable with an explanation ('This approval collects decision outputs — open it to decide.'); bulk selection excludes such rows via the existing actionable-count messaging.3. Approval-expression completion + inline root validation. The expression approver input gains the scope data-picker with three groups —
current.<field>(live at node entry),trigger.<field>(submit snapshot),vars.*(flow variables) — built byuseFlowScopefrom the same materials as the condition picker but with the approval root set (neverrecord.x/bare fields, which the runtime rejects).nodeOutputRefsnow models approval nodes (<nodeId>.decision+ declareddecisionOutputskeys) so the previous stage's outputs are pickable — including the exactvars.lead_review.next_reviewersthe headline scenario references — andvars.previousis always listed, fixing a false 'not a reference in scope' warning on flows with no other variables.Browser-verified (HMR console + framework#3554 worktree backend)
pending_approversresolved to exactly those two ids; flow completed.turbo type-checkgreen; app-shell tests 89 green.🤖 Generated with Claude Code